import pandas as pd

# 数独类
class Sudoku:

    # 从文件中读取数独数据，获取到一个二维数组
    def __init__(self):
        frame_data=[]
        for i in range(9):
            row=[0 for j in range(9)]
            frame_data.append(row)

        self.org_data = frame_data
        self.unresolved=dict()

    def print(self):
        for row in self.org_data:
            print(row)

    def load_from_csv(self,file_path='sudoku_data.csv'):
        """
        从csv文件中，读取数独数据（空值默认为0）
        :param file_path:
        :return:
        """
        df=pd.read_csv(file_path,header=None)

        df.fillna(0,inplace=True)

        df=df.astype(int)

        self.org_data=df.values
        pass


    # 列出未被解决的数格，并用列表表示 （row,col,choices）
    def calc_unresolved(self):
        for row in range(9):
            for col in range(9):
                # print(row,col)
                if self.org_data[row][col]==0:
                    if row not in self.unresolved:
                        self.unresolved[row]={}
                    self.unresolved[row][col]={1,2,3,4,5,6,7,8,9}
                pass

    def calc_group_rcs(self,row,col):
        rs=row//3*3
        cs=col//3*3
        return rs,cs

    # 对某个数格进行核算，排除不可能的值（如果排除到1，则填充到原始数据；如果排除到0，则抛出异常）
    def exclude_single(self,row,col):
        unres_set=self.unresolved[row][col]
        unres_len=len(unres_set)
        # 先排除行
        for c in range(9):
            val=self.org_data[row][c]
            if val>0:
                unres_set.discard(val)

        # 再排除列
        for r in range(9):
            val=self.org_data[r][col]
            if val>0:
                unres_set.discard(val)

        # 再排除单元格
        rs,cs=self.calc_group_rcs(row,col)
        for ri in range(3):
            r=rs+ri
            for ci in range(3):
                c=cs+ci
                val=self.org_data[r][c]
                if val>0:
                    unres_set.discard(val)

        if len(unres_set)==0:
            raise Exception('zero_unres')
        if len(unres_set)==1:
            self.org_data[row][col]=next(iter(unres_set))
            self.unresolved[row][col]=None

        # 返回消掉的数量
        return unres_len-len(unres_set)



    # 严谨刷新方法 （相当于对所有数格进行核算），返回值是本次操作数
    def exclude_all(self):
        ttl_excount=0
        for row in range(9):
            for col in range(9):
                if self.org_data[row][col]==0:
                    ttl_excount+=self.exclude_single(row,col)
        print(ttl_excount)
        return ttl_excount

    # 猜测刷新方法（创建一个数独副本，开始进行严谨刷新；出现异常则表示尝试失败，换另一个值重新猜测）
    def guess_exclude(self):
        """
        通过猜测排外。仅当严谨刷新方案无法解决问题时，再使用该方案。
        先找到未解决的策略中，待确定数量最小的策略（或者随机找一个也可以）
        之后创建一个新的sudoku,使用原来的已被严谨解决到最近的org_data，并根据未解决的值中，随机取一个值进行确定。
        这之后再次进行严谨刷新，可能会有3种结果：
        1. 数独被解决；（数独结果可能不唯一，此处仅给出一种）
        2. 数独出现异常（‘zero_unres’）；说明假定值是错误的，去掉原来的假定值，使用新的假定值。如果某个备选值集合被用完了，仍然报错，则说明数独是无解的。
        3. 数独仍未解决：在新的假定数独中，再创建一个假定数独
        :return:
        """
        pass

if __name__ == '__main__':
    sudoku=Sudoku()
    sudoku.print()
    sudoku.load_from_csv()
    sudoku.print()
    print('-----------')
    sudoku.calc_unresolved()
    excount=1
    while sudoku.exclude_all()>0:
        pass
    sudoku.print()
